﻿using nswer_sheet;
using OpenCvSharp;
using System;
using System.Collections.Generic;
using System.Linq;
using static System.Runtime.InteropServices.JavaScript.JSType;
using Point = OpenCvSharp.Point;
using Size = OpenCvSharp.Size;

public class MatSplitter
{


    public enum SplitDirection { Horizontal, Vertical }


    public class SplitResult
    {
        public Mat Image { get; set; }
        public Rect ROI { get; set; }  // 在原图中的位置

        public SplitResult(Mat image, Rect roi)
        {
            Image = image;
            ROI = roi;
        }
    }


    /// <summary>
    /// 找出列表中明显大于其他值的元素。
    /// </summary>
    /// <param name="values">原始数据</param>
    /// <param name="thresholdInStd">多少个标准差之外算“明显大”，默认 2</param>
    /// <returns>所有满足条件的元素</returns>
    public static List<double> FindSignificantlyLarger(IEnumerable<double> values,
                                                       double thresholdInStd = 2.0)
    {
        if (values == null || !values.Any())
            return new List<double>();

        var list = values.ToList();
        double avg = list.Average();
        double std = Math.Sqrt(list.Select(x => Math.Pow(x - avg, 2)).Average());

        // 避免 std == 0（所有值都一样）导致除零
        if (std == 0)
            return new List<double>();

        double upperBound = avg + thresholdInStd * std;

        return list.Where(v => v > upperBound).ToList();
    }

    /// <summary>
    /// 找出“明显高”的 GapInfo（高度显著大于其他 Gap）
    /// </summary>
    /// <param name="gaps">原始 Gap 列表</param>
    /// <param name="thresholdInStd">多少个标准差算异常，默认 2</param>
    /// <returns>所有明显高的 GapInfo</returns>
    public static List<GapInfo> FindSignificantlyHigherGaps(IEnumerable<GapInfo> gaps,
                                                            double thresholdInStd = 2.0)
    {
        if (gaps == null || !gaps.Any())
            return new List<GapInfo>();

        var list = gaps.ToList();
        var heights = list.Select(g => (double)g.Height).ToList();

        double avg = heights.Average();
        double std = Math.Sqrt(heights.Select(h => Math.Pow(h - avg, 2)).Average());

        // 避免除零
        if (std == 0)
            return new List<GapInfo>();

        double upperBound = avg + thresholdInStd * std;

        return list.Where(g => g.Height > upperBound).ToList();
    }
    public static Mat EnsureBinary(Mat input)
    {
        // 如果已经是单通道二值图像，直接返回副本
        if (input.Channels() == 1 && input.Type() == MatType.CV_8UC1)
        {
            // 检查是否已经是二值图像
            double minVal, maxVal;
            Cv2.MinMaxLoc(input, out minVal, out maxVal);
            if (minVal == 0 && maxVal == 255)
                return input.Clone();
        }

        // 转换为灰度图像
        Mat gray = new Mat();
        if (input.Channels() == 3)
        {
            Cv2.CvtColor(input, gray, ColorConversionCodes.BGR2GRAY);
        }
        else if (input.Channels() == 4)
        {
            Cv2.CvtColor(input, gray, ColorConversionCodes.BGRA2GRAY);
        }
        else
        {
            gray = input.Clone();
        }

        // 二值化处理
        Mat binary = new Mat();
        Cv2.Threshold(gray, binary, 0, 255, ThresholdTypes.Otsu | ThresholdTypes.Binary);

        // 清理临时资源
        if (gray != input) gray.Dispose();

        return binary;
    }


    /// <summary>
    /// 先水平、后垂直切分，返回所有最终子块的 ROI
    /// </summary>
    /// <param name="src">输入图像</param>
    /// <param name="maxHorizontal">水平方向最多切几块</param>
    /// <param name="maxVertical">垂直方向最多切几块（int.MaxValue 表示不限）</param>
    /// <returns>所有子块的 Rect 列表</returns>
    public static List<Rect> SplitHV(Mat src,
                                     int maxHorizontal = 13,
                                     int maxVertical = int.MaxValue)
    {
        var finalRects = new List<Rect>();
        var src1= EnsureBinary(src);
        // 1. 水平方向切
        var splitter = new MatSplitter();
        var hBlocks = splitter.SplitMat(src,
                                        MatSplitter.SplitDirection.Horizontal,
                                        maxHorizontal);

        // 2. 对每一块再垂直方向切
        foreach (var h in hBlocks)
        {
            Mat hRoiMat = src.Clone(h.ROI);   // 复制 ROI 区域
            //using Mat dilated = DilateImage(hRoiMat, 3, 1);
            // Cv2.BitwiseNot(hRoiMat, hRoiMat);
            //hRoiMat= Erode(hRoiMat);
            //Cv2.BitwiseNot(hRoiMat, hRoiMat);
            //hRoiMat= DilateImage(hRoiMat, 1,1);

            hRoiMat.SaveImage("R:\\HR.JPG");
            var vBlocks = splitter.SplitMat(hRoiMat,
                                            MatSplitter.SplitDirection.Vertical,
                                            maxVertical);

            // 注意：vBlocks 返回的 ROI 坐标是相对于 hRoiMat 的，
            // 需要把偏移加回去才能得到在原始 src 上的绝对坐标。
            foreach (var v in vBlocks)
            {
                Rect absRect = new Rect(
                    h.ROI.X + v.ROI.X,
                    h.ROI.Y + v.ROI.Y,
                    v.ROI.Width,
                    v.ROI.Height);
               
                finalRects.Add(absRect);
            }
        }

        return finalRects;
    }
    public static Mat DilateImage(Mat input, int width, int height)
    {
        Mat output = new Mat();
        Mat kernel = Cv2.GetStructuringElement(
            MorphShapes.Rect,
            new Size(width, height));
        Cv2.Dilate(input, output, kernel);
        return output;
    }
    public static Mat Erode(Mat src)
    {
        Mat kernel = Cv2.GetStructuringElement(
            MorphShapes.Rect,
            new Size(1, 2));  // 3x3矩形核

        // 4. 执行腐蚀操作
        Mat eroded = new Mat();
        Cv2.Erode(src, eroded, kernel);
        return eroded;
    }





    public List<SplitResult> SplitMat(Mat src, SplitDirection direction, int maxParts)
    {
        // 参数校验
        if (src.Empty())
            return new List<SplitResult> { new SplitResult(src.Clone(), new Rect(0, 0, src.Width, src.Height)) };

        if (maxParts < 1)
            throw new ArgumentException("maxParts必须大于0");

        // 转换为灰度并处理底色
        using Mat gray = src.Channels() == 3 ? src.CvtColor(ColorConversionCodes.BGR2GRAY) : src.Clone();
        using Mat binary = PreprocessImage(gray);

        // 根据方向调用不同的分割方法
        return direction == SplitDirection.Horizontal
            ? SplitHorizontally(binary, maxParts, src)
            : SplitVertically(binary, src);
    }

    private Mat PreprocessImage(Mat gray)
    {
        // 检测底色（白底或黑底）
        bool isWhiteBackground = DetectBackgroundColor(gray);

        // 二值化处理
        Mat binary = new Mat();
        Cv2.Threshold(gray, binary, 0, 255, ThresholdTypes.Binary | ThresholdTypes.Otsu);

        // 如果是白底，反转图像
        if (isWhiteBackground)
        {
            Cv2.BitwiseNot(binary, binary);
        }

        return binary;
    }

    private bool DetectBackgroundColor(Mat gray)
    {
        // 检测四角的像素值（假设角落是背景）
        int borderSize = 5;
        int whiteCount = 0;
        int totalCorners = 0;

        // 检查四个角落
        for (int y = 0; y < borderSize; y++)
        {
            for (int x = 0; x < borderSize; x++)
            {
                // 左上角
                if (gray.At<byte>(y, x) > 200) whiteCount++;
                // 右上角
                if (gray.At<byte>(y, gray.Width - 1 - x) > 200) whiteCount++;
                // 左下角
                if (gray.At<byte>(gray.Height - 1 - y, x) > 200) whiteCount++;
                // 右下角
                if (gray.At<byte>(gray.Height - 1 - y, gray.Width - 1 - x) > 200) whiteCount++;

                totalCorners += 4;
            }
        }

        // 如果超过70%的角落像素是白色，则认为是白底
        return (whiteCount * 100 / totalCorners) > 70;
    }

    private List<SplitResult> SplitHorizontally(Mat binary, int maxParts, Mat original)
    {
        // 计算水平投影
        int[] projection = CalculateProjection(binary, isHorizontal: true);
        int[] smoothed = SmoothProjection(projection, 2);

        // 找到所有有效间隔
        var gaps = FindGaps(smoothed, minGapHeight: 2, minSegmentHeight: 10);

        var gapsH = FindSignificantlyHigherGaps(gaps);


        // 检查分割段数是否超过maxParts
        if (gapsH.Count == 0 || gapsH.Count >= maxParts)
            return new List<SplitResult> { new SplitResult(original.Clone(), new Rect(0, 0, original.Width, original.Height)) };

        // 分割图像
        var result = new List<SplitResult>();
        int startY = 0;

        foreach (var gap in gapsH.OrderBy(g => g.Start))
        {
            int height = gap.Start - startY;
            if (height > 0)
            {
                Rect roi = new Rect(0, startY, original.Width, height);
                result.Add(new SplitResult(new Mat(original, roi), roi));
            }
            startY = gap.Start + gap.Height;
        }

        // 添加最后一段
        if (startY < binary.Height)
        {
            Rect roi = new Rect(0, startY, original.Width, original.Height - startY);
            result.Add(new SplitResult(new Mat(original, roi), roi));
        }

        return result;
    }

    private List<SplitResult> SplitVertically(Mat binary, Mat original)
    {
        binary.SaveImage("R:\\bin.JPG");
        //这里需要清理噪点

        // 计算垂直投影
        int[] projection = CalculateProjection(binary, isHorizontal: false);
        int[] smoothed = SmoothProjection(projection, 2);

        // 找到所有有效间隔
        var gaps = FindGaps(smoothed, minGapHeight: 2, minSegmentHeight: 2);
        gaps = FindSignificantlyHigherGaps(gaps);
        if (gaps.Count == 0)
            return new List<SplitResult> { new SplitResult(original.Clone(), new Rect(0, 0, original.Width, original.Height)) };

        // 分割图像
        var result = new List<SplitResult>();
        int startX = 0;

        foreach (var gap in gaps.OrderBy(g => g.Start))
        {
            int width = gap.Start - startX;
            if (width > 0)
            {
                Rect roi = new Rect(startX, 0, width, original.Height);
                result.Add(new SplitResult(new Mat(original, roi), roi));
            }
            startX = gap.Start + gap.Height;
        }

        // 添加最后一段
        if (startX < binary.Width)
        {
            Rect roi = new Rect(startX, 0, original.Width - startX, original.Height);
            result.Add(new SplitResult(new Mat(original, roi), roi));
        }

        return result;
    }

    private int[] CalculateProjection(Mat binary, bool isHorizontal)
    {
        int size = isHorizontal ? binary.Height : binary.Width;
        int[] projection = new int[size];

        for (int i = 0; i < size; i++)
        {
            using Mat line = isHorizontal ? binary.Row(i) : binary.Col(i);
            projection[i] = Cv2.CountNonZero(line);
        }

        return projection;
    }

    private int[] SmoothProjection(int[] projection, int kernelSize)
    {
        if (kernelSize < 3) return projection;

        int[] smoothed = new int[projection.Length];
        int radius = kernelSize / 2;

        for (int i = 0; i < projection.Length; i++)
        {
            int sum = 0;
            int count = 0;

            for (int j = -radius; j <= radius; j++)
            {
                int idx = i + j;
                if (idx >= 0 && idx < projection.Length)
                {
                    sum += projection[idx];
                    count++;
                }
            }

            smoothed[i] = sum / count;
        }

        return smoothed;
    }

    private List<GapInfo> FindGaps(int[] projection, int minGapHeight, int minSegmentHeight)
    {
        var gaps = new List<GapInfo>();
        double avg = projection.Average();
        int threshold = (int)(avg * 0.1); // 使用平均值的10%作为空白阈值

        bool inGap = false;
        int gapStart = 0;
        int segmentStart = 0;

        for (int i = 0; i < projection.Length; i++)
        {
            if (projection[i] <= threshold)
            {
                if (!inGap)
                {
                    inGap = true;
                    gapStart = i;
                }
            }
            else
            {
                if (inGap)
                {
                    int gapHeight = i - gapStart;
                    if (gapHeight >= minGapHeight)
                    {
                        int segmentHeight = gapStart - segmentStart;
                        if (segmentHeight >= minSegmentHeight)
                        {
                            gaps.Add(new GapInfo(gapStart, gapHeight));
                        }
                        segmentStart = i;
                    }
                    inGap = false;
                }
            }
        }

        return gaps;
    }

    public  struct GapInfo
    {
        public int Start { get; }
        public int Height { get; }

        public GapInfo(int start, int height)
        {
            Start = start;
            Height = height;
        }
    }
}